/*
 * Resource.java   (8.aug.07)
 *
 * @author      Lobsang Alvites Szaler (l.alvites@lpcsite.com)
 * @version     0.30
 *
 * Copyright (c) Soluciones Info Móviles SAC
 *
 */

package sim.framework;

import java.util.*;
import javax.microedition.lcdui.*;
import java.io.*;

import sim.utils.*;
import sim.framework.*;
import sim.framework.io.*;


public class Resource implements FileGets {
    
    private Simlet  m_Simlet;
    
    // Resource Settings
    private String  m_File;     // res. filename to find (string) or load (image)
    private int     m_Type;     // resousrce type (STRING or IMAGE)
    private int     m_Cache;    // max.cache capacity before objects reloads
    private Vector  m_vecRes;   // vector containing Res.Objects
    
    // Variables to extract the parsed values
    private int     m_String_ofst;
    private int     m_String_len;
    
    private byte[]  m_abID = new byte[8];   // a textual representation of ID (int)
    private int     m_abID_len;             // length of valid data in m_adID
    private byte[]  m_abLine;               // to be used by fileGetsLine()
    
    // Default Error Message
    public  static final String  IMG_ERROR = "error.png";
    private static final byte[]  m_NotFound = "Res.Err.".getBytes();
    private byte[] m_tmp = new byte[ m_NotFound.length + 8 ];
    
    // Default Resource RES_PATH
    public static final    String  RES_PATH = "/"; // default resource RES_PATH
    private byte[]          m_abyte = new byte[8];  // max.length for textual ID
    
    // Resource TYPE Constants
    public final static int TYPE_STRING = 1;
    public final static int TYPE_IMAGE  = 2;
    
    // Resource CACHE default
    public final static int RES_CACHE_DEFAULT = 10;
    
    // Resource EXTENSIONS
    private static final byte   EXT_RES = 0;
    private static final byte   EXT_SAR = 1;
    private static final String m_Ext[] = { ".res", ".sar" };
    
    // Resource LANUAGES
    private static final String mem_lang[] = { "en", "es", "pt" };
    
    
    //==================================================================
    // IN-JAR LABELS CONSTANTS (always distributed with the JAR package)
    // Strings are located in "labels.sar" file.
    //==================================================================
    
    // Titulos
    public final static int LBL_TITLE = 0;
    public final static int LBL_TITLE_APP = LBL_TITLE + 1; // Título de la aplicación.
    // Etiquetas para notificaciones de error (La aplicación termina inesperadamente)
    public final static int LBL_FAIL = 10;
    public final static int LBL_FAIL_STORE = LBL_FAIL + 1; // Error de almacenamiento.
    public final static int LBL_FAIL_STORE_NOTFOUND = LBL_FAIL + 2; // Archivo no encontrado
    public final static int LBL_FAIL_STORE_FULLDISK = LBL_FAIL + 3; // No ha espacio en disco para
    public final static int LBL_FAIL_STORE_IOERROR = LBL_FAIL + 4; // Error lectura/escritura
    public final static int LBL_FAIL_INSTALL = LBL_FAIL + 5; // Error de instalación.
    public final static int LBL_FAIL_INITIALIZATION = LBL_FAIL + 6; // Error inicilizando Applet.
    // Etiquetas para mensajes
    public final static int LBL_MESG = 20;
    public final static int LBL_MESG_INSTALLING = LBL_MESG + 1; // Instalando ...
    public final static int LBL_MESG_INSTALLOK = LBL_MESG + 2; // Instalación OK, debe reiciar.
    public final static int LBL_MESG_AUTH_OK = LBL_MESG + 3; // Autentiación aceptada.
    public final static int LBL_MESG_AUTH_ERR = LBL_MESG + 4; // Autenticación rechazada.
    public final static int LBL_MESG_PASS = LBL_MESG + 5; // Password
    public final static int LBL_MESG_PASS_BAD = LBL_MESG + 6; // Clave erronea.
    public final static int LBL_MESG_SYNC_QUESTION   = LBL_MESG + 7; // Desea sincronizar?
    public final static int LBL_MESG_SYNC_CONNETING = LBL_MESG + 8; // Connectando ...
    public final static int LBL_MESG_SYNC_NETWORK_ERR = LBL_MESG + 9; // Red no disponible.
    public final static int LBL_MESG_SYNC_SERVER_ERR = LBL_MESG + 10; // Servidor no disponible.
    public final static int LBL_MESG_SYNC_SOCKET_ERR = LBL_MESG + 11; // Error de comunicación.
    public final static int LBL_MESG_SYNC_STARTING = LBL_MESG + 12; // Iniciando ...
    public final static int LBL_MESG_QUIT_QUESTION = LBL_MESG + 13; // Desea salir?
    public final static int LBL_MESG_WAIT = LBL_MESG + 14; // Espere ...
    public final static int LBL_MESG_SEARCHING = LBL_MESG + 15; // Buscando ...
    public final static int LBL_MESG_BEGIN = LBL_MESG + 16; // Inicio
    public final static int LBL_MESG_END = LBL_MESG + 17; // Fin
    public final static int LBL_MESG_WITHOUT_RESULTS = LBL_MESG + 18; // No hay coicidencias.
    public final static int LBL_MESG_LOST_CHANGE = LBL_MESG + 19; // Desea perder lo cambios?
    // Etiquetas para controles.
    public final static int LBL_CTRL = 60;
    public final static int LBL_CTRL_NEXT = LBL_CTRL + 1; // Sig. (Abreviatura de siguiente).
    public final static int LBL_CTRL_BACK = LBL_CTRL + 2; // Atrás
    public final static int LBL_CTRL_ACCEPT = LBL_CTRL + 3; // Aceptar
    public final static int LBL_CTRL_CANCEL = LBL_CTRL + 4; // Cancelar.
    public final static int LBL_CTRL_YES = LBL_CTRL + 5; // Si
    public final static int LBL_CTRL_NO = LBL_CTRL + 6;	// No
    public final static int LBL_CTRL_QUIT = LBL_CTRL + 7; // Salir de Aplicación.
    public final static int LBL_CTRL_PREF = LBL_CTRL + 8; // Pref. (Abreviatura de Preferencias.)
    
    public final static int LBL_USER = 100;
    
    //==================================================================
    // IMAGE CONSTANTS
    //==================================================================
    public final static int IMG                 = 0;
    public final static int IMG_ABOUT		= IMG + 1;
    public final static int IMG_QUIT		= IMG + 2;
    public final static int IMG_LOGOUT		= IMG + 3;
    public final static int IMG_SYNC		= IMG + 4;
    public final static int IMG_SEARCH		= IMG + 5;
    public final static int IMG_SAVE		= IMG + 6;
    public final static int IMG_TRASH		= IMG + 7;
    public final static int IMG_SUPPORT		= IMG + 8;
    public final static int IMG_OK              = IMG + 9;
    public final static int IMG_CANCEL		= IMG + 10;
    public final static int IMG_BACK		= IMG + 11;
    public final static int IMG_NEXT		= IMG + 12;
    public final static int IMG_PAGDOWN		= IMG + 13;
    public final static int IMG_PAGUP		= IMG + 14;
    public final static int IMG_INFO		= IMG + 15;
    public final static int IMG_APPEND		= IMG + 16;
    public final static int IMG_REMOVE		= IMG + 17;
    public final static int IMG_CHECK		= IMG + 18;
    public final static int IMG_MSGUSR		= IMG + 19;
    public final static int IMG_UNCHK		= IMG + 20;
    public final static int IMG_CLIENTE		= IMG + 51;
    public final static int IMG_PIDE		= IMG + 52;
    public final static int IMG_COBRA		= IMG + 53;
    public final static int IMG_ENTREGA		= IMG + 54;
    public final static int IMG_CAMBIA		= IMG + 55;
    public final static int IMG_DEVUELVE	= IMG + 56;
    public final static int IMG_REPORTA		= IMG + 57;
    public final static int IMG_NOATENCION	= IMG + 58;
    public final static int IMG_ENCUESTA	= IMG + 59;
    public final static int IMG_B4S_E00		= IMG + 60;
    public final static int IMG_B4S_0C0		= IMG + 61;
    public final static int IMG_B4S_EC0		= IMG + 62;
    public final static int IMG_B4S_00P		= IMG + 63;
    public final static int IMG_B4S_E0P		= IMG + 64;
    public final static int IMG_B4S_0CP		= IMG + 65;
    public final static int IMG_B4S_ECP		= IMG + 66;
    public final static int IMG_NUEVOCLIENTE    = IMG + 67;
    public final static int IMG_PROPROM             = IMG + 68;
    public final static int IMG_ENTREGAINCIDENCIA   = IMG + 69;
    public final static int IMG_ENTREGAREVISA       = IMG + 70;
    public final static int IMG_RUTA                = IMG + 71;
    //------------------------------------------------------------------
    //  END CONSTANTS
    //------------------------------------------------------------------
    
    /*
     *  Constructicon
     */
    public Resource( final Simlet s ) {
        m_Simlet = s;
    }
    
    /*
     *  Creates a new resource.
     *  If the cache value is higher than the internal resource's cache
     *  (RES_CACHE_DEFAULT) then uses the new value.
     *
     *  @param  fname   filename of the resource
     *  @param  type    type of the resource
     *  @param  cache   cache (or 0 to use the internal preset)
     */
    public boolean create( final String fname, final int type, final int cache ) {
        boolean bRes = false;
        
        m_vecRes = new Vector(RES_CACHE_DEFAULT);
        
        if( fname != null && m_vecRes != null ) {
            m_File = fname;
            m_Type = type;
            m_Cache = cache < RES_CACHE_DEFAULT ? RES_CACHE_DEFAULT : cache;
            
            // if requested resource is Label (Strings) resource, then checks
            // if RMS file (SAR) already exists, otherwise create it from text file.
            if( m_Type == TYPE_STRING ) {
                if( File.exists( (fname + m_Ext[EXT_SAR]) ) == false ) {
                    
                    int bLang = 0;
                    String str = System.getProperty("microedition.locale");
                    if( str != null ) {
                        str = str.substring(0,2);
                        for( int i=0; i<mem_lang.length; i++ ) {
                            if( str.equals(mem_lang[i]) == true ) {
                                bLang = i;
                                break;
                            }
                        }
                    }
                    // Creates an RMS "labels.sar" from "/labels-??.sar" file
                    str = ( RES_PATH + fname + "-" + mem_lang[ bLang ] + m_Ext[EXT_SAR] );
                    File.create( m_Simlet, str, (fname + m_Ext[EXT_SAR]), File.TYPE_TXT );
                }
            }
            bRes = true;
        }
        
        return bRes;
    }
    
    /*
     *  Loads a String from a Resource file and returns it.
     *  If the string was not found then returns an Error message containing
     *  the resource ID not found.
     *
     *  @param  id  ID of the label to load
     *  @param  ext extension type (EXT_RES or EXT_SAR)
     *  @return The loaded string
     */
    private String load_string( final int id, final int ext ) {
        boolean     bFound = false;
        FileStream  file = null;
        String      str = null;
        
        if( ext == EXT_RES ) {
            // Creates a text representation of the int ID to compare
            m_abID_len = Utils.fmtInteger(m_abID, id, 10, 0, Utils.FMT_LEFT);
        }
        
        file = (FileStream)FileStream.create(m_Simlet, (m_File+m_Ext[ext]), File.MODE_READ);
        if( file != null ) {
            // Processes each line
            file.gets( (FileGets)this );
            // If not EOF then label was found!
            if( !file.eof() ) {
                bFound = true;
            }
            file.close();
        }
        
        // If ID was not found at 2nd. attempt (SAR) then creates ERROR string
        if( bFound == false && ext == EXT_SAR ) {
            // Builds an error message with the ID not found
            m_String_ofst = 0;
            m_String_len = (m_NotFound.length + m_abID_len);
            Utils.memcpy( m_tmp, 0, m_NotFound, 0, m_NotFound.length );
            Utils.memcpy( m_tmp, m_NotFound.length, m_abID, 0, m_abID_len );
            m_abLine = m_tmp;
        }
        
        // If ID was found (at 1st or 2nd attempt) or was not found in SAR (2nd)
        // then returns a String containing either the label found or Res.Err.#
        if( bFound == true || ext == EXT_SAR ) {
	    str = Utils.decoding(m_abLine, m_String_ofst, m_String_len, m_Simlet.getAppEncoding());
        }
        
        return str;
    }
    
    /*
     *  A "callback" from FileGets class
     */
    public boolean fileGetsLine( final byte[] abyte, int len, final int lenEOL ) {
        boolean bRes = true;
        int     res = 0;
        int     lenID;      // length of ID field value (from offset 0 to Token)

	if (len > lenEOL) {        
	    // Strips CRLF characters
	    len -= lenEOL;
	    
	    // Gets the length of ID field (by the way, checks the validity of the
	    // Resource line format by locating the token ':' from offset 1.
	    // In other words, ID field is from offset 0 to token's offset).
	    lenID = Utils.memmem( abyte, 0, len, Utils.TOKEN, Utils.TOKEN_COLON, 1 );
	    if( lenID >= 1 ) {
		// If both IDs have the same length, then check if are the same value
		// Compares if this line is the ID requested
		if( m_abID_len == lenID ) {
		    res = Utils.memmem( abyte, 0, lenID, m_abID, m_abID_len );
		    if( res > -1 && res < lenID ) {
			// ID found. Remember Label offset to retrieve it later
			m_String_ofst = (lenID + 1);    // adds 1 char TOKEN (':')
			m_String_len = (len - m_String_ofst);
			m_abLine = abyte;
			bRes = false;
		    }
		}
	    }
	}

        return bRes;
    }
    
    /*
     *  Loads an Image from the Resource and returns it.
     *  If the image was not found then returns an Error image.
     *
     *  @param  id  ID of the image to load
     *  @return The loaded imaged
     */
    private Image load_image( final int id ) {
        Image       img = null;
        try {
            int digits = Utils.fmtInteger(m_abyte, id, 10, 2, Utils.FMT_LEFT);
            img = Image.createImage( RES_PATH + m_File + new String(m_abyte,0,digits) + ".png" );
        } catch( Exception _ex ) {
            try {
                img = Image.createImage( RES_PATH + IMG_ERROR );
            } catch( Exception _ex2 ) {}
        }
        return img;
    }
    
    /*
     *  Loads and store a resource object in this Resource container
     *
     *  @param  id  ID of the resource to load
     *  @return Resource Structure
     */
    private Resource_s load( final int id ) {
        Resource_s  res_s = null;
        Object      obj = null;
        
        // Opens the file and searches the requested resource
        switch( m_Type ) {
            case TYPE_STRING:
                obj = (Object)load_string( id, EXT_RES );
                if( obj == null ) {
                    obj = (Object)load_string( id, EXT_SAR );
                }
                //#ifdef DEBUG_RESOURCE
                //#             System.out.println(" id : "+id);
                //#             System.out.println(" str: "+(String)obj);
                //#endif
                break;
            case TYPE_IMAGE:
                obj = (Object)load_image( id );
                break;
        }
        
        // A new Resource Object was loaded. Now insert it in the cache.
        if( obj != null ) {
            // If vector has reached the cache limit then delete the
            // last object (the less used)
            if( m_vecRes.size() == m_Cache ) {
                res_s = ((Resource_s)m_vecRes.elementAt(m_Cache-1));
                m_vecRes.removeElementAt(m_Cache-1);
                //#if DEBUG_RESOURCE_CACHE
                //#                 System.out.println(" id: "+id+" relocated at head");
                //#endif
            } else {
                res_s = new Resource_s();
                //#if DEBUG_RESOURCE_CACHE
                //#                 System.out.println(" id: "+id+" new Resource allocated");
                //#endif
            }
        }
        
        // Prepare the Res.Object containing the new object loaded or created
        res_s.m_id = id;
        res_s.m_data = obj;
        
        return res_s;
    }
    
    /*
     *  Gets a resource from the cache. If not found then load it from file.
     *
     *  @param  id  ID of the requested object
     *  @return
     */
    public synchronized Object getResource( final int id ) {
        Resource_s  res_s = null;
        int         i;
        int         j;
        
        //#ifdef DEBUG_RESOURCE
        //# System.out.println("Resource::getResource() ENTERED");
        //#endif
        
        //#ifdef DEBUG_RESOURCE
        //# System.out.println("\tid = " + id);
        //#endif
        
        if( m_vecRes == null ) {
            return null;
        }
        
        // Locates the Res.Object in the cache and delete it
        for( i = 0, j = m_vecRes.size(); i < j; i++ ) {
            if( id == ((Resource_s)m_vecRes.elementAt(i)).m_id ) {
                // Object found! Get it and remove it from the cache
                //#if DEBUG_RESOURCE
                //#                 System.out.println(" id: "+id+" in Cache. Removing and Relocated at head");
                //#endif
                res_s = ((Resource_s)m_vecRes.elementAt(i));
                // Kill vector
                m_vecRes.removeElementAt(i);
                i = j;
            }
        }
        
        // If not found, loads the new resource from file
        if( res_s == null ) {
            res_s = load( id );
        }
        
        // Store Res.Object (either the found or a new one) at head of Vector
        m_vecRes.insertElementAt( res_s, 0 );
        
        //#ifdef DEBUG_RESOURCE
        //# System.out.println("Resource::getResource() EXITING");
        //#endif
        
        return res_s.m_data;
    }
    
    
    
    // -------------------------------------------------------
    //  RESOURCE DATA STRUCTURE
    // -------------------------------------------------------
    
    private class Resource_s {
        private int     m_id;       // unique ID of the object stored
        private Object  m_data;     // object's reference
    }
    
    // -------------------------------------------------------
    
}
